/*
 * router.c - packet router
 * Clunix, KyungWoon Cho, cezanne@clunix.com, 2006.2
 *
 * Copyright (c) 2001-2008 by Clunix Inc. All Rights Reserved.
 */

#include "config.h"

#include "ebcd.h"

static ecm_uint32	n_routers;
static ECMBOOL		stopping_routers;
static ecm_mutex_t	routers_mutex;
static ecm_cond_t	routers_empty_cond;

static ECM_INLINE ECMBOOL
pktb_has_valid_dst(ebc_pktb_t *pktb)
{
	ebc_ndid_t	dst_ndid;

	dst_ndid = pktb->pkthdr.mhdr.dstaddr.ndid;
	if (dst_ndid == ebcd_ndid || EBC_NDID_IS_BROADCAST(dst_ndid) ||
	    ebcd_has_epch(dst_ndid))
		return ECM_TRUE;
	return ECM_FALSE;
}

static void
route_rpktb(ebcd_pch_t *dpch, ebc_pktb_t *pktb)
{
	ebc_msgt_t	msgt = pktb->pkthdr.mhdr.msgt;

	if (msgt & EBC_MSGT_ALIVE)
		ebc_pktb_free(pktb);
	else if (msgt & EBC_MSGT_DETECT)
		process_detection(dpch, pktb);
	else if (pktb_has_valid_dst(pktb))
		ebcd_process_pktb(dpch, pktb);
	else
		ebcd_drop_pktb(pktb);
}

static void
do_route(ecm_ptr_t arg)
{
	ebcd_pch_t	*dpch = (ebcd_pch_t *)arg;
	ebc_pktb_t	*pktb;

	ecm_mutex_lock(routers_mutex);

	while (!stopping_routers && !list_empty(&dpch->routing_pktbs)) {
		pktb = list_entry(dpch->routing_pktbs.next, ebc_pktb_t, list);
		list_del_init(&pktb->list);
		ECMASSERT(dpch->n_routing_pktbs > 0);
		dpch->n_routing_pktbs--;
		ecm_mutex_unlock(routers_mutex);
		route_rpktb(dpch, pktb);
		ecm_mutex_lock(routers_mutex);
	}

	ECMASSERT(n_routers > 0);
	n_routers--;
	if (n_routers == 0)
		ecm_cond_signal(routers_empty_cond);
	dpch->routing = ECM_FALSE;

	ecm_mutex_unlock(routers_mutex);
	ebcd_pch_put(dpch);
}

void
ebcd_add_lazy_pktb(ebcd_pch_t *dpch, ebc_pktb_t *pktb)
{
	ecm_mutex_lock(routers_mutex);
	if (!dpch->routing) {
		if (!ebcd_pch_get(dpch)) {
			ecm_mutex_unlock(routers_mutex);
			ebc_pktb_free(pktb);
			return;
		}
		if (ecm_start_worker(ebcd_core_workers, do_route, dpch)) {
			dpch->routing = ECM_TRUE;
			n_routers++;
		}
	}

	list_add_tail(&pktb->list, &dpch->routing_pktbs);
	dpch->n_routing_pktbs++;
	ecm_mutex_unlock(routers_mutex);
}

void
ebcd_start_router(void)
{
	routers_mutex = ecm_mutex_create(ECM_FALSE);
	routers_empty_cond = ecm_cond_create();
}

void
ebcd_stop_router(void)
{
	ecm_mutex_lock(routers_mutex);
	stopping_routers = ECM_TRUE;
	while (n_routers > 0)
		ecm_cond_wait(routers_empty_cond, routers_mutex, NULL);
	ecm_mutex_unlock(routers_mutex);

	ecm_mutex_free(routers_mutex);
	ecm_cond_free(routers_empty_cond);
	stopping_routers = ECM_FALSE;
}
